package org.crud4j.core.defprovider;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

import org.apache.log4j.Logger;
import static org.apache.commons.beanutils.PropertyUtils.*;

import org.crud4j.core.CrudBeanDef;
import org.crud4j.core.CrudFieldDef;

/**
 * Factory of providers.
 */
public class CrudBeanDefFactory
{

    private static Logger log = Logger.getLogger(CrudBeanDefFactory.class);
    /**
     * Definition providers
     */
    private static Collection<CrudBeanDefProvider> providers = new ArrayList<CrudBeanDefProvider>();
    /**
     * default provider
     */
    private static CrudBeanDefProvider defaultProvider = new AnnCrudDefProvider();

    /**
     * Adds a custom provider to the factory
     * 
     * @param provider
     */
    public static void registerProvider(CrudBeanDefProvider provider)
    {
        if (!providers.contains(provider))
        {
            providers.add(provider);
        } else
        {
            log.warn("trying to register an already registered provider " + provider.getClass().getName());
        }
    }
    /**
     * Definitions cache
     */
    private static Map<Class, CrudBeanDef> cache = new HashMap<Class, CrudBeanDef>();

    /**
     * Creates the definition for the given type
     * 
     * @param type
     * @return
     */
    public static CrudBeanDef getDefinition(Class type)
    {
        if (cache.containsKey(type))
        {
            return cache.get(type);
        }
        CrudBeanDef def = null;
        synchronized (cache)
        {

            def = new CrudBeanDef();
            def.setType(type);
            try
            {
                // Add fields to the def
                Map<String, CrudFieldDef> fields = new HashMap<String, CrudFieldDef>();
                Object bean = type.newInstance();
                Map<String, Object> properties = describe(bean);
                properties.remove("class");
                for (String key : properties.keySet())
                {
                    // FIXME check other methods ?
                    Method m = getPropertyDescriptor(bean, key).getReadMethod();
                    // Create a CrudField
                    CrudFieldDef field = new CrudFieldDef();
                    field.setName(key);

                    fields.put(field.getName(), field);
                }
                def.setFieldDefsMap(fields);
            } catch (Exception e)
            {
                throw new RuntimeException(e);
            }
        }


        // update def with registered providers
        for (CrudBeanDefProvider provider : providers)
        {
            provider.updateDef(type, def);
        }

        // default provider overrides previous defs
        defaultProvider.updateDef(type, def);

        cache.put(type, def);
        return def;
    }
}

